﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using Tron.DataStructures;
using Tron.Players.Simple;
using Tron.Players.Search;

namespace Tron.Players
{
    class PlayerManager
    {
        Game game;
        Board board;

        List<Player> players;
        List<PlayerState> playerStates;

        ReadOnlyCollection<PlayerState> readOnlyplayerStates;

        Percepts percepts;

        public List<Player> Players
        {
            get
            {
                return players;
            }
        }

        public ReadOnlyCollection<PlayerState> PlayerStates
        {
            get
            {
                return readOnlyplayerStates;
            }
        }

        public int AliveCount { get; private set; }
        public List<int> PlayerScores { get; private set; }

        bool gameOver = false;

        public PlayerManager(Game game, List<Color> playerColors, List<PlayerTypesEnum> playerTypes, 
            List<Vector2> playerStartingPositions, List<Vector2> playerStartingDirections, Board board,
            bool firstGame, List<Player> oldPlayersIfNotFirstGame, List<String> playerNames)
        {
            this.game = game;
            this.board = board;

            //players = new List<Player>();
            playerStates = new List<PlayerState>();

            readOnlyplayerStates = new ReadOnlyCollection<PlayerState>(playerStates);

            for (int playerIndex = 0; playerIndex < playerColors.Count; playerIndex++)
            {
                playerStates.Add(new PlayerState(game, playerIndex, playerColors[playerIndex], 
                    playerStartingPositions[playerIndex], playerStartingDirections[playerIndex], board, playerNames[playerIndex]));
            }

            if (firstGame)
            {
                players = new List<Player>();
                for (int playerIndex = 0; playerIndex < playerColors.Count; playerIndex++)
                {
                    players.Add(MakeNewPlayer(playerIndex, playerTypes[playerIndex]));
                }
            }
            else
            {
                players = oldPlayersIfNotFirstGame;
            }

            foreach(PlayerState playerState in playerStates)
            {
                Vector2 position = playerState.Position;
                board.SetBlockValue((int)position.X, (int)position.Y, playerState.PlayerIndex, playerState.Alive);
            }

            percepts = new Percepts(board, playerStates.ConvertAll<IPlayerStatePercepts>(PlayerState.IPlayerStatePerceptsToPlayerState));

            // Let the still alive players decide actions.
            foreach (Player player in players)
            {
                if (playerStates[player.PlayerIndex].Alive == true)
                {
                    player.Update(percepts, playerStates[player.PlayerIndex].Actions);
                }
            }

            // Add the next animation for the players that are still alive.
            foreach (PlayerState playerState in playerStates)
            {
                if (playerState.Alive)
                    playerState.DoNextAnimation();
            }

            foreach (Player player in players)
            {
                player.NewGame();
            }
        }

        Player MakeNewPlayer(int playerIndex, PlayerTypesEnum playerType)
        {
            Player newPlayer;
            switch (playerType)
            {
                case PlayerTypesEnum.ReflexAgent:
                    newPlayer = new ReflexAgentPlayer(playerIndex);
                    break;
                case PlayerTypesEnum.OpenSpace:
                    newPlayer = new OpenSpacePlayer(playerIndex);
                    break;
                case PlayerTypesEnum.Human:
                    newPlayer = new HumanPlayer(playerIndex);
                    break;
                case PlayerTypesEnum.IterativeDeepeningAlphaBetaSearch:
                    newPlayer = new IterativeDeepeningAlphaBetaSearchPlayer(playerIndex);
                    break;
                case PlayerTypesEnum.IDSMiniMaxSearch:
                    newPlayer = new IDSMiniMaxSearchPlayer(playerIndex);
                    break;
                case PlayerTypesEnum.ReinforcementLearner:
                    newPlayer = new ReinforcementLearner(playerIndex);
                    break;
                default:
                    throw new Exception("Can't create a player of the requested type.");
            }
            return newPlayer;
        }

        public void Step()
        {
            // Advance the position of the players that are still alive.
            foreach (PlayerState playerState in playerStates)
            {
                if(playerState.Alive)
                    playerState.DoMove();
            }

            // Check for player-player collisions.
            foreach (PlayerState outerPlayerState in playerStates)
            {
                foreach (PlayerState innerPlayerState in playerStates)
                {
                    if (outerPlayerState.Position == innerPlayerState.Position &&
                        outerPlayerState.PlayerIndex != innerPlayerState.PlayerIndex)
                    {
                        outerPlayerState.SetAlive(false);
                        innerPlayerState.SetAlive(false);
                    }
                }
            }

            // Update the state of the board.
            foreach (PlayerState playerState in playerStates)
            {
                Vector2 position = playerState.Position;
                board.SetBlockValue((int)position.X, (int)position.Y, playerState.PlayerIndex, playerState.Alive);
            }

            // Let the still alive players decide actions.
            foreach (Player player in players)
            {
                if (playerStates[player.PlayerIndex].Alive == true)
                {
                    player.Update(percepts, playerStates[player.PlayerIndex].Actions);
                }
            }

            // Add the next animation for the players that are still alive.
            foreach (PlayerState playerState in playerStates)
            {
                if (playerState.Alive)
                    playerState.DoNextAnimation();
            }

            if (!gameOver)
            {
                AliveCount = 0;
                foreach (PlayerState playerState in playerStates)
                {
                    if (playerState.Alive)
                        AliveCount++;
                }

                if (AliveCount == 0)
                {
                    // The game is over

                    int maximumMoveCount = 0;
                    foreach (PlayerState playerState in playerStates)
                    {
                        if (playerState.Moves.Count > maximumMoveCount)
                            maximumMoveCount = playerState.Moves.Count;
                    }

                    int numberOfPlayersWithMaximumMoveCount = 0;
                    foreach (PlayerState playerState in playerStates)
                    {
                        if (playerState.Moves.Count == maximumMoveCount)
                            numberOfPlayersWithMaximumMoveCount++;
                    }

                    PlayerScores = new List<int>(players.Count);
                    foreach (PlayerState playerState in playerStates)
                    {
                        if (playerState.Moves.Count == maximumMoveCount)
                        {
                            if (numberOfPlayersWithMaximumMoveCount == 1)
                            {
                                PlayerScores.Add(1);
                            }
                            else
                            {
                                PlayerScores.Add(0);
                            }
                        }
                        else
                        {
                            PlayerScores.Add(-1);
                        }
                    }
                    gameOver = true;
                    foreach (Player player in players)
                    {
                        player.GameOver(percepts, PlayerScores);
                    }
                    if (Constants.GameType != GameTypesEnum.Repeat)
                    {
                        String endOfGameString = "Game Over\n";
                        for (int i = 0; i < PlayerScores.Count; i++)
                        {
                            int score = PlayerScores[i];
                            endOfGameString += Constants.PlayerNames[i] + " scored: " + score + "\n";
                        }
                        (game as TronGame).TextGraphics.Text = endOfGameString;
                    }
                }
            }
        }
    }
}
